﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Sasa.QualityTools.CodeAnalysis.Common;
using System.Xml;
using System.Globalization;

namespace Sasa.QualityTools.CodeAnalysis.Core.Models
{
    public class MetricsReport : ICodeAnalysisReport
    {
        private object loadLockKey;


        public ICollection<MetricValue> MetricValues { get; private set; }

        public ICodeAnalysisResultValueFilter Filter { get; set; }


        public MetricsReport()
        {
            loadLockKey = new object();
            MetricValues = new List<MetricValue>();
        }


        public void Load(string path)
        {
            if (!File.Exists(path))
            {
                var message = Text.Format("The specified file '{0}' doesn't exist.", path);
                throw new InvalidOperationException(message);
            }

            try
            {
                lock (loadLockKey)
                {
                    MetricValues.Clear();

                    var document = new XmlDocument();
                    document.Load(path);
                    LoadMetricValues(document);
                }
            }
            catch (Exception e)
            {
                var message = Text.Format("Failed to load the specified Metrics report '{0}'.", path);
                throw new CodeAnalysisException(message, e);
            }
        }

        private void LoadMetricValues(XmlNode root)
        {
            foreach (XmlNode metricsNode in root.SelectNodes("//Metrics"))
            {
                var metricValue = new MetricValue();
                UpdateMetricValueWithXmlNode(metricValue, metricsNode);
                if (Filter == null || Filter.IsAvailable(metricValue))
                {
                    MetricValues.Add(metricValue);
                }
            }
        }

        private int? ParseMeticValue(XmlNode metricsNode, string name)
        {
            var xpath = String.Format("Metric[@Name='{0}']", name);
            var node = metricsNode.SelectSingleNode(xpath);
            if (node != null)
            {
                return Int32.Parse(node.Attributes["Value"].Value, NumberStyles.AllowThousands);
            }
            else
            {
                return null;
            }
        }

        private CodeKind GetCodeKind(XmlNode metricsNode)
        {
            return (CodeKind)Enum.Parse(typeof(CodeKind), metricsNode.SelectSingleNode("..").Name);
        }

        private void UpdateMetricValueWithXmlNode(MetricValue metricValue, XmlNode node)
        {
            switch (node.Name)
            {
                case "Target":
                    metricValue.AssemblyPath = node.Attributes["Name"].Value;
                    break;
                case "Module":
                    UpdateMetricValueWithXmlNode(metricValue, node.SelectSingleNode("../.."));
                    break;
                case "Namespace":
                    metricValue.Namespace = node.Attributes["Name"].Value;
                    UpdateMetricValueWithXmlNode(metricValue, node.SelectSingleNode("../.."));
                    break;
                case "Type":
                    metricValue.TypeName = node.Attributes["Name"].Value;
                    UpdateMetricValueWithXmlNode(metricValue, node.SelectSingleNode("../.."));
                    break;
                case "Member":
                    metricValue.Signature = node.Attributes["Name"].Value;
                    metricValue.Path = node.SelectSingleNode("@File").GetValueOrDefault(null);
                    metricValue.Line = Int32.Parse(node.SelectSingleNode("@Line").GetValueOrDefault("1"), NumberStyles.AllowThousands);
                    UpdateMetricValueWithXmlNode(metricValue, node.SelectSingleNode("../.."));
                    break;
                case "Metrics":
                    metricValue.MaintainabilityIndex = ParseMeticValue(node, "MaintainabilityIndex").Value;
                    metricValue.CyclomaticComplexity = ParseMeticValue(node, "CyclomaticComplexity").Value;
                    metricValue.ClassCoupling = ParseMeticValue(node, "ClassCoupling").Value;
                    metricValue.DepthOfInheritance = ParseMeticValue(node, "DepthOfInheritance");
                    metricValue.LinesOfCode = ParseMeticValue(node, "LinesOfCode").Value;
                    metricValue.Kind = GetCodeKind(node);
                    UpdateMetricValueWithXmlNode(metricValue, node.SelectSingleNode(".."));
                    break;
            }
        }
    }
}
